﻿module CssExpander

open CssTypes

(* Expands the shorthand syntax in css to full properties *)


let join (map1:Map<'a,'b>) (map2:Map<'a,'b>) = Map.fold (fun acc key value -> Map.add key value acc) map1 map2

///  expanded set op css declarations
///   (i.e. no shorthand declarations)
type StyleSpec = StyleSpec of Map<string,Term>
    with
    member this.map =
        let (StyleSpec map) = this
        map

    member this.join(spec:StyleSpec) =
        let map2 = join this.map spec.map
        StyleSpec(map2)

    (*
        Update styles
    *)
    member this.updateStyles(spec:StyleSpec) =
        let map = join this.map spec.map
        StyleSpec(map)

let addRules (spec:StyleSpec) rules : StyleSpec =
    let map = rules |> List.fold (fun (map:Map<string,Term>) rule-> map.Add rule) spec.map 
    StyleSpec(map)


let addRule (spec:StyleSpec) rule =
        StyleSpec(spec.map.Add(rule))

let addBorderWidth (map:StyleSpec) top right bottom left =
            addRules map [
                "border-top-width", top
                "border-right-width", right
                "border-bottom-width", bottom
                "border-left-width", left]

let addBorderColor (map:StyleSpec) top right bottom left =
             addRules map [
                "border-top-color", top
                "border-right-color", right
                "border-bottom-color", bottom
                "border-left-color", left]

let addBorderStyle (map:StyleSpec) top right bottom left =
             addRules map [
                "border-top-style", top
                "border-right-style", right
                "border-bottom-style", bottom
                "border-left-style", left]

let addMargin (map:StyleSpec) top right bottom left =
             addRules map [
                "margin-top", top
                "margin-right", right
                "margin-bottom", bottom
                "margin-left", left]

let addPadding (map:StyleSpec) top right bottom left =
             addRules map [
                "padding-top", top
                "padding-right", right
                "padding-bottom", bottom
                "padding-left", left]

let expandDeclarations (map:StyleSpec) (Declaration(name, terms, importance)) : StyleSpec = 
    (* 
        here we should split composit declarations, eg padding to padding-top, padding-bottom etc 
        each decl should end up with a single value.
    *)
    let oneToFour terms = 
        match terms with
            | [top;right;bottom;left] -> Some(top, right, bottom, left)
            | [top;horizontal;bottom] -> Some(top, horizontal, bottom, horizontal)
            | [vertical;horizontal] -> Some(vertical, horizontal, vertical, horizontal)
            | [all] -> Some(all, all, all, all)
            | _ -> None   
    match name with
    | "margin" ->
        match oneToFour terms with
        | Some(top, right, bottom, left) -> addMargin map top right bottom left
        | None -> map
    | "padding" -> 
        match oneToFour terms with
        | Some(top, right, bottom, left) -> addPadding map top right bottom left
        | None -> map
    | "border-width" -> 
        match oneToFour terms with
        | Some(top, right, bottom, left) -> addBorderWidth map top right bottom left
        | None -> map
    | "border-color" -> 
        match oneToFour terms with
        | Some(top, right, bottom, left) -> addBorderColor map top right bottom left
        | None -> map
    | "border-style" -> 
        match oneToFour terms with
        | Some(top, right, bottom, left) -> addBorderStyle map top right bottom left
        | None -> map
    | "border" ->         
        match terms with
        (* TODO needs to handle missing options *)
        | [width;style;color] -> 
            let map = addBorderWidth map width width width width
            let map = addBorderStyle map style style style style
            let map = addBorderColor map color color color color
            map
        | _ -> map // ignore other configurations
    | "font" -> 
        (*
            font: [ style || variant || weight ] size (/ line ?) family 
            font: keyword
        *)

        let findOrDefault option dflt list =
            defaultArg (list |> List.tryFind ((=) option)) dflt

        // read terms in reverse because the optionals are first        
        // size and family are required - size may include line-height
        match List.rev terms with
        | (family::size::rest) ->
            
            let fontSize, lineHeight =
                match size with 
                | Slash(size, lineHeight) -> size, lineHeight
                | term -> term, IdentifierTerm("normal")
            
            // the rest are optional and can appear in any order
            let fontStyle = rest |> findOrDefault (IdentifierTerm("italic")) (IdentifierTerm("normal"))
            
            // only normal and small-caps allowed here, even if more options are allowed for font-variant
            let fontVariant = rest |> findOrDefault (IdentifierTerm("small-caps")) (IdentifierTerm("normal"))
            
            let fontWeight = rest |> findOrDefault (IdentifierTerm("bold")) (IdentifierTerm("normal"))
            
            let map = addRule map ("font-size", fontSize)
            let map = addRule map ("line-height", lineHeight)
            let map = addRule map ("font-family", family)
            let map = addRule map ("font-style", fontStyle)
            let map = addRule map ("font-variant", fontVariant)
            let map = addRule map ("font-weight", fontWeight)
            map
        | _ -> map // ignore other configurations
    // TODO: background shorthand
    | _ -> 
        let value = terms.[0]
        addRule map (name, value)

type Rule1 = Rule1 of Selector list * StyleSpec
type RuleSet1 = Rule1 list
let toStyleSpec declarations = 
    let rules = declarations |> List.fold expandDeclarations (StyleSpec Map.empty)
    StyleSpec(rules.map)




